﻿//    nVLC
//    
//    Author:  Roman Ginzburg
//
//    nVLC is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    nVLC is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//    GNU General Public License for more details.
//     
// ========================================================================

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Timers;
using Declarations;
using Implementation.Utils;
using LibVlcWrapper;

namespace Implementation
{
   internal sealed unsafe class MemoryRenderer : DisposableBase, IMemoryRenderer
   {
      IntPtr m_hMediaPlayer;
      NewFrameEventHandler m_callback = null;
      BitmapFormat m_format;
      Timer m_timer = new Timer();
      volatile int m_frameRate = 0;
      object m_lock = new object();
      List<Delegate> m_callbacks = new List<Delegate>();

      IntPtr pLockCallback;
      IntPtr pUnlockCallback;
      IntPtr pDisplayCallback;

      GCHandle m_pixelDataPtr = default(GCHandle);
      PixelData m_pixelData;
      void* m_pBuffer = null;

      public MemoryRenderer(IntPtr hMediaPlayer)
      {
         m_hMediaPlayer = hMediaPlayer;

         LockEventHandler leh = OnpLock;
         UnlockEventHandler ueh = OnpUnlock;
         DisplayEventHandler deh = OnpDisplay;

         pLockCallback = Marshal.GetFunctionPointerForDelegate(leh);
         pUnlockCallback = Marshal.GetFunctionPointerForDelegate(ueh);
         pDisplayCallback = Marshal.GetFunctionPointerForDelegate(deh);

         m_callbacks.Add(leh);
         m_callbacks.Add(deh);
         m_callbacks.Add(ueh);

         m_timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
         m_timer.Interval = 1000;
      }

      void timer_Elapsed(object sender, ElapsedEventArgs e)
      {
         m_frameRate = 0;       
      }

      unsafe void* OnpLock(void* opaque, void** plane)
      {
         PixelData* px = (PixelData*)opaque;
         *plane = px->pPixelData;
         return null;
      }

      unsafe void OnpUnlock(void* opaque, void* picture, void** plane)
      {

      }

      unsafe void OnpDisplay(void* opaque, void* picture)
      {
         lock (m_lock)
         {
            PixelData* px = (PixelData*)opaque;
            MemoryHeap.CopyMemory(m_pBuffer, px->pPixelData, px->size);

            m_frameRate++;
            if (m_callback != null)
            {
               using (Bitmap frame = GetBitmap())
               {
                  m_callback(frame);
               }
            }
         }
      }

      private Bitmap GetBitmap()
      {
         return new Bitmap(m_format.Width, m_format.Height, m_format.Pitch, m_format.PixelFormat, new IntPtr(m_pBuffer));
      }

      #region IMemoryRenderer Members

      public void SetCallback(NewFrameEventHandler callback)
      {
         m_callback = callback;
      }

      public void SetFormat(BitmapFormat format)
      {
         m_format = format;
         
         LibVlcMethods.libvlc_video_set_format(m_hMediaPlayer, m_format.Chroma.ToUtf8(), m_format.Width, m_format.Height, m_format.Pitch);
         m_pBuffer = MemoryHeap.Alloc(m_format.ImageSize);
         
         m_pixelData = new PixelData(m_format.ImageSize);
         m_pixelDataPtr = GCHandle.Alloc(m_pixelData, GCHandleType.Pinned);
         LibVlcMethods.libvlc_video_set_callbacks(m_hMediaPlayer, pLockCallback, pUnlockCallback, pDisplayCallback, m_pixelDataPtr.AddrOfPinnedObject());
      }

      internal void StartTimer()
      {
         m_timer.Start();
      }

      public int ActualFrameRate 
      {
         get
         {
            return m_frameRate;
         }
      }

      public Bitmap CurrentFrame 
      {
         get
         {
            lock (m_lock)
            {
               return GetBitmap();
            }
         }
      }

      #endregion

      protected override void Dispose(bool disposing)
      {
         if (disposing)
         {
            m_timer.Dispose();
         }

         IntPtr zero = IntPtr.Zero;
         LibVlcMethods.libvlc_video_set_callbacks(m_hMediaPlayer, zero, zero, zero, zero);

         m_pixelDataPtr.Free();
         m_pixelData.Dispose();

         MemoryHeap.Free(m_pBuffer);
      }
   }
}
